/*
 * Dijjer - A Peer to Peer HTTP Cache
 * Copyright (C) 2004,2005 Change.Tv, Inc, Mathias De Maré
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package dijjer.updater;

import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import dijjer.io.comm.AbstractPeer;
import dijjer.io.comm.Message;
import dijjer.util.AbstractThread;
import dijjer.util.logging.Logger;

public abstract class AbstractResourceManager implements NonUpdateable {

	public static final String VERSION = "$Id: ResourceManager.java 2 2008-03-21 14:33:03Z ian.clarke $";
	protected String mcastAddr = null;
	public int mcastPort = 0;
	
	protected String _downloadURL = "http://downloads.dijjer.org/dijjer.jar";
	protected File _classCacheDirectory;
	protected File _dataDirectory;
	protected boolean _shutdown = false;
	
	private final Map _TCPSockets = Collections.synchronizedMap(new HashMap());
	protected final List _validAddresses = new ArrayList();
	
	public int getMulticastPort() {
		return mcastPort;
	}
	
	public String getMulticastAddress() {
		return mcastAddr;
	}
	
    public void addValidAddress(String address)
    {
        _validAddresses.add(address);
    }
    
    public abstract boolean validAddress(String addr);
    
    public abstract File downloadCodeBase() throws Exception;
    
    public void setDownloadURL(String downloadURL) {
		_downloadURL = downloadURL;
	}
    
    public File getRuntimeBase() throws Exception {
		if (_classCacheDirectory == null) {
			return null;
		}
		File[] files = _classCacheDirectory.listFiles(new FilenameFilter() {
			public boolean accept(File dir, String name) {
				return name.endsWith(".jar");
			}
		});
		if (files.length == 0) {
			return downloadCodeBase();
		}
		File recent = null;

		for (int i = 0; i < files.length; i++) {
			File file = files[i];
			if (recent == null || file.lastModified() > recent.lastModified()) {
				recent = file;
			}
		}
		return recent;
	}
    
    public static void cleanupCache() {
	}

	public void setClassCacheDirectory(File classCacheDirectory) {
		Logger.init("Class cache:" + classCacheDirectory);
		_classCacheDirectory = classCacheDirectory;
	}

	public void setDataDirectory(File dataDirectory) {
		Logger.init("Data cache:" + dataDirectory);
		_dataDirectory = dataDirectory;
	}

	public File getDataDirectory() {
		return _dataDirectory;
	}

	public String getDownloadURL() {
		return _downloadURL;
	}
	
	private void createTCPSocket(int port) {
		checkShutdown();
		synchronized (_TCPSockets) {
			if (!_TCPSockets.containsKey(new Integer(port))) {
				try {
					_TCPSockets.put(new Integer(port), new TCPSocket(port) {
							{
							startThread();
							}
							});
				} catch (Exception e) {
					Logger.fatal(-1, "Couldn't connect to TCP port " + port + 
					                 ", is another instance of running on the same port?");
				}
			}
		}
	}
	
	public abstract Message receive(int port);
	
	public abstract Message receiveMC(int port);
	
	public abstract void send(AbstractPeer destination, Message packet);
	
	public Socket accept(int port) {
		createTCPSocket(port);
		TCPSocket socket = (TCPSocket) _TCPSockets.get(new Integer(port));
		return socket.accept();
	}
	
	public void softShutdown() {
		_shutdown = true;
	}
	
	public void shutdown() {
		_shutdown = true;
		for (Iterator i = _TCPSockets.values().iterator(); i.hasNext();) {
			TCPSocket tcpSocket = (TCPSocket) i.next();
			tcpSocket.shutdownThread();
		}
	}
	
	protected void checkShutdown() {
		if (_shutdown) {
			throw new RuntimeException("Resource manager is shut down!");
		}
	}
	
	public void revive() {
		_shutdown = false;
	}
	
	protected class TCPSocket extends AbstractThread {

		private final ServerSocket _socket;
		private final List _sockets = new ArrayList(15);

		public TCPSocket(int port) throws Exception {
			long wait = 2;
			ServerSocket socket = null;
			while (socket == null) {
				try {
					socket = new ServerSocket(port);
				} catch (IOException e) {
					if (wait == 2) {
						Logger.info("Attempting to shutdown a previous dijjer on port: " + port);
						URL shutdown = new URL("http://127.0.0.1:" + port + "/shutdown?confirm=yes");
						shutdown.getContent();
					}
					
					Logger.warning("Could not bind to socket on port: " + port + ", trying again in " + wait + " seconds.");
					Thread.sleep(wait * 1000);
					wait *= wait;
				}
			}
			_socket = socket;
		}

		public Socket accept() {
			synchronized (_sockets) {
				while (_sockets.isEmpty() && !wasShutdown() && !wasShutdown()) {
					checkShutdown();
					try {
						_sockets.wait(2000);
					} catch (InterruptedException ex) {
						return null;
					}
				}
				return (Socket) _sockets.remove(0);
			}
		}

		protected boolean loop() throws InterruptedException {
			try {
				Socket socket = _socket.accept();
				synchronized (_sockets) {
					_sockets.add(socket);
					_sockets.notifyAll();
				}
			} catch (SocketException e) {
				if (wasKilled() || wasShutdown()) {
					Logger.info("Shutting down TCP socket");
				} else {
					Logger.error("Problem receiving TCP request!", e);
				}
			} catch (IOException e) {
				Logger.error("Problem receiving TCP request!", e);
			}
			return true;
		}

		protected void cleanUp() {
			if (!_sockets.isEmpty()) {
				Logger.warning("Dumping " + _sockets.size() + " unused packets.");
			}
			_sockets.clear();
		}

		protected void shutdown() {
			try {
				_socket.close();
			} catch (Exception expected) {
				Logger.debug("Junk exception: ", expected);
			}
		}
	}
	
}
